package controller

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mini-app/config"
	"mini-app/config/redis"
	"mini-app/internal/app/grpc/client"
	"mini-app/internal/app/grpc/protoc/advertisingc"
	"mini-app/internal/app/grpc/protoc/devicec"
	"mini-app/internal/app/grpc/protoc/sale_adminc"
	"mini-app/internal/app/grpc/protoc/system_admin"
	"mini-app/internal/app/web/service"
	"mini-app/internal/dal"
	"mini-app/internal/dao"
	"mini-app/internal/define"
	"mini-app/internal/pkg"
	"net/http"
	"os"
	"os/exec"
	"path"
	"strings"
	"time"

	"192.168.1.75/go-pkg/errorx"
	"192.168.1.75/go-pkg/helper"
	"192.168.1.75/go-pkg/logx"
	"192.168.1.75/go-pkg/response"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	wechatv3 "github.com/go-pay/gopay/wechat/v3"
	redisx "github.com/go-redis/redis/v8"
	"github.com/qichengzx/coordtransform"
)

type Advertisement struct{}

func GeoRadius(longitude, latitude *float64, radius float32) (geoLocation []redisx.GeoLocation, err error) {
	rdb := redis.NewDB()
	key := define.RedisGeo + config.Config.MerchantID
	var lng, lat float64
	if longitude == nil && latitude == nil {
		lng = 114.058342
		lat = 22.553623
	} else {
		lng = *longitude
		lat = *latitude
	}
	lng, lat = coordtransform.GCJ02toWGS84(lng, lat)
	geoLocation, err = rdb.GeoRadius(context.Background(), key, lng, lat, &redisx.GeoRadiusQuery{
		Radius:    float64(radius), // 范围100km
		Unit:      "km",            // 单位km
		Sort:      "ASC",           // 由近到远
		WithCoord: true,            // 返回经纬度坐标
		WithDist:  true,            // 返回距离
	}).Result()
	return
}

// 微信登录获取openid【code置换】
func (Advertisement) Login(c *gin.Context) {
	code := c.Query("code")
	token, phone, err := service.Advertisement{}.Login(c.Request.Context(), code)
	response.JSON(c, gin.H{
		"token": token,
		"phone": phone,
	}, err)
}
func (Advertisement) GetQrCode(c *gin.Context) {
	var params struct {
		Page      string `json:"page"`
		Scene     string `json:"scene"`
		Env       string `json:"env"`
		IsHyaline bool   `json:"is_hyaline"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	data, err := pkg.MiniProgram{}.GetUnlimitedQRCodeAdvertisement(c.Request.Context(), params.Page, params.Scene, params.Env, params.IsHyaline)
	response.JSON(c, data, err)
}

// 获取手机号【code置换】
func (Advertisement) GetPhoneNumber(c *gin.Context) {
	openid := c.GetString("openid")
	code := c.Query("code")
	data, err := service.Advertisement{}.GetPhoneNumber(c.Request.Context(), openid, code)
	response.JSON(c, data, err)
}

// 创建订单
func (Advertisement) CreateOrder(c *gin.Context) {
	var params struct {
		CustomName  string `json:"customName"`
		PhoneNumber string `json:"phoneNumber"`
		DeviceIDs   string `json:"deviceIDs"`
		StartTime   int64  `json:"startTime"`
		EndTime     int64  `json:"endTime"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	openid := c.GetString("openid")

	data, err := service.Advertisement{}.CreateOrder(c.Request.Context(), openid, params.CustomName, params.PhoneNumber, params.DeviceIDs, params.StartTime, params.EndTime)
	response.JSON(c, data, err)
}

func (Advertisement) CreateOrderBySchedule(c *gin.Context) {
	var params struct {
		CustomName        string `json:"customName"`
		PhoneNumber       string `json:"phoneNumber"`
		DeviceIDs         string `json:"deviceIDs"`
		ScheduleID        string `json:"scheduleID"`
		MerchantID        string `json:"merchantID"`
		SupportActivityID string `json:"support_activity_id"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	openid := c.GetString("openid")

	data, err := service.Advertisement{}.CreateOrderBySchedule(
		c.Request.Context(),
		openid,
		params.CustomName,
		params.PhoneNumber,
		params.DeviceIDs,
		params.ScheduleID,
		params.MerchantID,
		params.SupportActivityID,
	)
	response.JSON(c, data, err)
}

func (Advertisement) OrderAll(c *gin.Context) {
	var params struct {
		ID                string `json:"id"`
		OrderStatus       int    `json:"order_status"`
		Page              int    `json:"page"`
		Limit             int    `json:"limit"`
		StartTime         int64  `json:"start_time"`
		EndTime           int64  `json:"end_time"`
		CustomName        string `json:"customName"`
		PhoneNumber       string `json:"phoneNumber"`
		ScheduleStatus    int    `json:"schedule_status"`
		SupportActivityID string `json:"support_activity_id"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)

	list, total, err := service.Advertisement{}.OrderList(
		c.Request.Context(),
		params.ID,
		"",
		params.OrderStatus,
		params.StartTime,
		params.EndTime,
		params.CustomName,
		params.PhoneNumber,
		params.ScheduleStatus,
		params.SupportActivityID,
		params.Page,
		params.Limit,
	)

	response.JSON(c, gin.H{
		"total": total,
		"list":  list,
	}, err)
}

// 获取订单列表
func (Advertisement) OrderList(c *gin.Context) {
	var params struct {
		ID                string `json:"id"`
		OrderStatus       int    `json:"order_status"`
		Page              int    `json:"page"`
		Limit             int    `json:"limit"`
		StartTime         int64  `json:"start_time"`
		EndTime           int64  `json:"end_time"`
		CustomName        string `json:"customName"`
		PhoneNumber       string `json:"phoneNumber"`
		ScheduleStatus    int    `json:"schedule_status"`
		SupportActivityID string `json:"support_activity_id"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	openid := c.GetString("openid")

	list, total, err := service.Advertisement{}.OrderList(
		c.Request.Context(),
		params.ID,
		openid,
		params.OrderStatus,
		params.StartTime,
		params.EndTime,
		params.CustomName,
		params.PhoneNumber,
		params.ScheduleStatus,
		params.SupportActivityID,
		params.Page,
		params.Limit,
	)

	response.JSON(c, gin.H{
		"total": total,
		"list":  list,
	}, err)
}

// 获取订单详情
func (Advertisement) OrderDetail(c *gin.Context) {
	var params struct {
		ID string `json:"id"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)

	data, err := service.Advertisement{}.OrderDetail(c.Request.Context(), params.ID)

	response.JSON(c, data, err)
}

// 修改Remark
func (Advertisement) OrderModify(c *gin.Context) {
	var params struct {
		ID              string `json:"id"`
		Status          int    `json:"order_status"`
		Remark          string `json:"remark"`
		AdvertisementID string `json:"advertisement_id"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)

	err := service.Advertisement{}.OrderModify(c.Request.Context(), params.ID, params.Status, params.AdvertisementID, params.Remark)
	response.JSON(c, gin.H{}, err)
}

// WechatJspay 调取微信的jsapi支付，返回相关参数供前端使用
func (Advertisement) WechatJSPay(c *gin.Context) {
	var params struct {
		ID string `json:"id"` // 订单号
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	openid := c.GetString("openid")
	data, err := service.Advertisement{}.WechatJSPay(c.Request.Context(), params.ID, openid)
	response.JSON(c, data, err)
}

// 手动退款
func (Advertisement) Refund(c *gin.Context) {
	var params struct {
		OutTradeNo string `json:"out_trade_no"`
		Remarks    string `json:"remarks"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)

	createUserID := c.GetString("user_id")
	createUsername := c.GetString("username")

	logx.Info(context.Background(), "Refund Advertisement", logx.Any("createUserID", createUserID), logx.Any("username", createUsername), logx.Any("out_trade_no", params.OutTradeNo), logx.Any("remarks", params.Remarks))
	data, err := service.Advertisement{}.Refund(
		c.Request.Context(),
		params.OutTradeNo,
		params.Remarks,
		createUserID,
		createUsername,
	)
	response.JSON(c, data, err)
}

func (Advertisement) RefundList(c *gin.Context) {
	var params struct {
		OrderID   string `json:"order_id"`
		StartTime int64  `json:"start_time"`
		EndTime   int64  `json:"end_time"`
		Page      int    `json:"page"`
		Limit     int    `json:"limit"`
		UserID    string `json:"user_id"`
		UserName  string `json:"user_name"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	openid := c.GetString("openid")

	list, total, err := service.Advertisement{}.RefundList(
		c.Request.Context(),
		params.OrderID,
		params.StartTime,
		params.EndTime,
		openid,
		params.UserID,
		params.UserName,
		params.Page,
		params.Limit,
	)
	response.JSON(c, gin.H{
		"total": total,
		"list":  list,
	}, err)
}

// 微信支付回调
func (Advertisement) WechatV3NotifyAd(c *gin.Context) {
	pkg.WechatPay{}.V3ConfigInit("", "", "")
	resource, err := wechatv3.V3ParseNotify(c.Request)
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
			// 错误码，SUCCESS为清算机构接收成功，其他错误码为失败。
			"code": "SUCCESS",
			"msg":  "failed to parse request body",
		})
		return
	}

	result, err := resource.DecryptPayCipherText(config.Config.WechatV3Config.APIv3Key)
	logx.Info(context.Background(), "WechatV3NotifyAd", logx.Any("result.TradeState", result.TradeState))
	if err != nil {
		c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
			// 错误码，SUCCESS为清算机构接收成功，其他错误码为失败。
			"code": "SUCCESS",
			"msg":  "failed to parse request body",
		})
		return
	}

	// 处理订单状态为已支付
	if result.TradeState == "SUCCESS" {
		err := dao.Advertisement{}.OrderStatusChange(c.Request.Context(), dal.Q, pkg.ExtractOrderID(result.OutTradeNo), define.AdOrderStatusPaid)
		if err == nil {
			paidYuan := float32(result.Amount.Total) / 100.0
			dao.Advertisement{}.OrderPaidAmountChange(c.Request.Context(), dal.Q, pkg.ExtractOrderID(result.OutTradeNo), paidYuan)

			dao.Advertisement{}.SetPaidID(c.Request.Context(), dal.Q, pkg.ExtractOrderID(result.OutTradeNo), result.OutTradeNo)

			detail, err0 := dao.Advertisement{}.Detail(c.Request.Context(), dal.Q, pkg.ExtractOrderID(result.OutTradeNo))
			if err0 == nil {
				//推送创单告警消息

				startAtStr := time.Unix(detail.StartAt, 0).Format("2006-01-02 15:04:05")
				endAtStr := time.Unix(detail.EndAt, 0).Format("2006-01-02 15:04:05")

				deviceIDsep := strings.Split(detail.DeviceIDs, ",")
				for _, deviceInfo := range deviceIDsep {

					deviceID := strings.Split(deviceInfo, ":")[0]
					logx.Info(context.Background(), "WechatV3NotifyAd", logx.Any("Send Notify OrderID", detail.ID), logx.Any("deviceID", deviceID))
					alarmMsg := map[string]interface{}{
						"alarm_type": 3003,
						"robot_id":   deviceID,
						"content":    "新广告订单 ID:" + detail.ID + " 起始时间：" + startAtStr + " 结束时间：" + endAtStr,
						"created_at": time.Now().Unix(),
					}
					jsonString, _ := json.Marshal(alarmMsg)
					rdb := redis.NewDB()
					rdb.LPush(c.Request.Context(), "system:alarm-notify-list", string(jsonString))
				}
			}
		}

		client, err := client.GetAdvertisingClient()
		if err != nil {
			return
		}
		data, err := service.Advertisement{}.OrderDetail(c.Request.Context(), pkg.ExtractOrderID(result.OutTradeNo))

		var params2 advertisingc.SPublishScheduleSetOrderIDReq
		params2.Id = data.ScheduleID
		params2.OrderId = data.ID
		params2.PayAmount = data.PayAmount
		params2.PaidAmount = data.PaidAmount
		params2.DiscountAmount = data.DiscountAmount
		params2.CustomName = data.CustomName
		params2.PhoneNumber = data.PhoneNumber
		params2.OutTradeNo = result.OutTradeNo
		_, err = client.ScheduleSetOrderID(c.Request.Context(), &params2)
		if err != nil {
			return
		}
	}

	// 返回处理结果
	c.XML(http.StatusOK, nil)
}

// GetDeviceFaceCount retrieves the face count for a specific device from the daily report
func GetDeviceFaceCount(ctx context.Context, deviceID string) (int64, error) {
	return 0, nil
}
func GetDeviceFaceCountReal(ctx context.Context, deviceID string) (int64, error) {
	// Get sale admin client
	saleAdminCli, err := client.GetSaleAdminClient()
	if err != nil {
		return 0, err
	}
	if saleAdminCli == nil {
		return 0, err
	}

	// Calculate time range for last month
	now := time.Now()
	endTime := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, now.Location()).Unix()
	startTime := time.Date(now.Year(), now.Month()-1, now.Day(), 0, 0, 0, 0, now.Location()).Unix()

	// Create daily report request
	dailyReportReq := &sale_adminc.DailyReportReq{
		StartTime: startTime,
		EndTime:   endTime,
		RobotId:   deviceID,
		GroupType: 1, // Group by machine
		DataType:  2, // Exposure/gaze data
		Page:      1,
		Limit:     1,
	}

	// Get daily report
	dailyReportResp, err := saleAdminCli.DailyReport(ctx, dailyReportReq)
	if err != nil {
		return 0, fmt.Errorf("failed to get daily report: %v", err)
	}

	// Check if we have any results
	if len(dailyReportResp.List) == 0 {
		return 0, nil
	}

	// Get face count from the first report
	dailyReport := dailyReportResp.List[0]
	return int64(dailyReport.Facecount), nil
}

// AllDeviceAd 所有设备
func (Advertisement) AllDeviceAd(c *gin.Context) {
	var req struct {
		RobotID      string  `form:"robot_id"`
		Page         int32   `form:"page"`
		Limit        int32   `form:"limit"`
		Online       int32   `form:"online"`
		SubMode      int32   `form:"sub_mode"`
		Longitude    float64 `form:"longitude"`
		Latitude     float64 `form:"latitude"`
		DeviceTypes  string  `form:"device_types"`
		MerchantIds  string  `form:"merchant_ids"`
		ProvinceID   string  `form:"province_id"`
		CityID       string  `form:"city_id"`
		DistrictID   string  `form:"district_id"`
		LocationID   string  `form:"location_id"`
		MerchantID   string  `form:"merchant_id"`
		MerchantPath string  `form:"merchant_path"`
	}

	if err := c.ShouldBindQuery(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	cli, err := client.GetDeviceAdminClient()
	if cli == nil && err != nil {
		c.JSON(http.StatusOK, gin.H{
			"total": 0,
		})
		return
	}

	grpcReq := &devicec.DeviceListAdReq{
		RobotId:      req.RobotID,
		Page:         req.Page,
		Limit:        req.Limit,
		Online:       req.Online,
		SubMode:      req.SubMode,
		Longitude:    req.Longitude,
		Latitude:     req.Latitude,
		DeviceTypes:  req.DeviceTypes,
		MerchantIds:  req.MerchantIds,
		ProvinceId:   req.ProvinceID,
		CityId:       req.CityID,
		DistrictId:   req.DistrictID,
		LocationId:   req.LocationID,
		MerchantId:   req.MerchantID,
		MerchantPath: req.MerchantPath,
	}

	resp, err := cli.AllDeviceAd(c.Request.Context(), grpcReq)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	basePrice, err := dao.AdConfigDao{}.GetBasePrice(c.Request.Context(), dal.Q)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Convert devices to map array
	deviceList := make([]map[string]interface{}, 0, len(resp.Devices))
	for _, device := range resp.Devices {

		faceCount, err := GetDeviceFaceCount(c.Request.Context(), device.RobotId)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		name, saleoff, start_at, end_at, _ := service.AdCoupon{}.GetCouponByContition(c.Request.Context(), time.Now().Unix(), device.RobotId, "")

		deviceMap := map[string]interface{}{
			"id":                  device.Id,
			"robot_id":            device.RobotId,
			"mac_addr":            device.MacAddr,
			"merchant_id":         device.MerchantId,
			"merchant_path":       device.MerchantPath,
			"identification_code": device.IdentificationCode,
			"device_type":         device.DeviceType,
			"device_ip":           device.DeviceIp,
			"location_id":         device.LocationId,
			"floor_id":            device.FloorId,
			"position_id":         device.PositionId,
			"current_map_name":    device.CurrentMapName,
			"latest_active_time":  device.LatestActiveTime,
			"service_phone":       device.ServicePhone,
			"firmware_version":    device.FirmwareVersion,
			"battery":             device.Battery,
			"created_at":          device.CreatedAt,
			"updated_at":          device.UpdatedAt,
			"device_type_title":   device.DeviceTypeTitle,
			"online":              device.Online,
			"location_name":       device.LocationName,
			"location_latitude":   device.LocationLatitude,
			"location_longitude":  device.LocationLongitude,
			"floor_name":          device.FloorName,
			"merchant_name":       device.MerchantName,
			"mode":                device.Mode,
			"mode_name":           device.ModeName,
			"sub_mode":            device.SubMode,
			"sub_mode_name":       device.SubModeName,
			"addr":                device.Addr,
			"position_name":       device.PositionName,
			"dist_km":             device.DistKm,
			"opening_hours":       device.OpeningHours,
			"coupon": map[string]interface{}{
				"name":       name,
				"sales_off":  saleoff,
				"base_price": basePrice,
				"start_at":   start_at,
				"end_at":     end_at,
			},
			"face_count": faceCount,
			"images":     device.Images,
		}
		deviceList = append(deviceList, deviceMap)
	}
	response.JSON(c, gin.H{
		"total": resp.Total,
		"list":  deviceList,
	}, err)

	return

}

// DeviceListAd 设备列表
func (Advertisement) DeviceListAd(c *gin.Context) {
	var req struct {
		RobotID      string  `form:"robot_id"`
		Page         int32   `form:"page"`
		Limit        int32   `form:"limit"`
		Online       int32   `form:"online"`
		SubMode      int32   `form:"sub_mode"`
		Longitude    float64 `form:"longitude"`
		Latitude     float64 `form:"latitude"`
		DeviceTypes  string  `form:"device_types"`
		MerchantIds  string  `form:"merchant_ids"`
		ProvinceID   string  `form:"province_id"`
		CityID       string  `form:"city_id"`
		DistrictID   string  `form:"district_id"`
		LocationID   string  `form:"location_id"`
		MerchantID   string  `form:"merchant_id"`
		MerchantPath string  `form:"merchant_path"`
	}

	if err := c.ShouldBindQuery(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	if req.Latitude == 0 || req.Longitude == 0 {

		cli, err := client.GetDeviceAdminClient()
		if cli == nil && err != nil {
			c.JSON(http.StatusOK, gin.H{
				"total": 0,
			})
			return
		}
		grpcReq := &devicec.DeviceListAdReq{
			RobotId:      req.RobotID,
			Page:         req.Page,
			Limit:        req.Limit,
			Online:       req.Online,
			SubMode:      req.SubMode,
			Longitude:    req.Longitude,
			Latitude:     req.Latitude,
			DeviceTypes:  req.DeviceTypes,
			MerchantIds:  req.MerchantIds,
			ProvinceId:   req.ProvinceID,
			CityId:       req.CityID,
			DistrictId:   req.DistrictID,
			LocationId:   req.LocationID,
			MerchantId:   req.MerchantID,
			MerchantPath: req.MerchantPath,
		}

		resp, err := cli.DeviceListAd(context.Background(), grpcReq)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		basePrice, err := dao.AdConfigDao{}.GetBasePrice(c.Request.Context(), dal.Q)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		//test code start-------------------------------------------------
		// name1, _, _, _, _ := service.AdCoupon{}.GetCouponByContition(c.Request.Context(), time.Now().Unix(), "DDRetail109", "")
		// if name1 == "" {

		// }
		// deviceIDsep := strings.Split("Brynntest:天虹君尚购物中心:G3:13,DDRetail139:天虹君尚购物中心:G3:12", ",")

		// deviceIDs_Input := ""
		// for _, deviceIDAndInfo := range deviceIDsep {
		// 	deviceIDOnly := strings.Split(deviceIDAndInfo, ":")[0]
		// 	deviceIDs_Input += deviceIDOnly
		// 	deviceIDs_Input += ","
		// }

		// totalPrice, err := service.AdCoupon{}.GetTotalPriceByCoupon(c.Request.Context(), 1753812800, 1753977599, deviceIDs_Input, "")
		// if totalPrice > 0 {

		// }
		//test code end-------------------------------------------------

		// Convert devices to map array
		deviceList := make([]map[string]interface{}, 0, len(resp.Devices))
		for _, device := range resp.Devices {
			faceCount, err := GetDeviceFaceCount(c.Request.Context(), device.RobotId)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
			name, saleoff, start_at, end_at, _ := service.AdCoupon{}.GetCouponByContition(c.Request.Context(), time.Now().Unix(), device.RobotId, "")

			logx.Info(context.Background(), "DeviceListAd", logx.Any("devicd.RobotId", device.RobotId), logx.Any("couponname", name), logx.Any("saleoff", saleoff))
			deviceMap := map[string]interface{}{
				"id":                  device.Id,
				"robot_id":            device.RobotId,
				"mac_addr":            device.MacAddr,
				"merchant_id":         device.MerchantId,
				"merchant_path":       device.MerchantPath,
				"identification_code": device.IdentificationCode,
				"device_type":         device.DeviceType,
				"device_ip":           device.DeviceIp,
				"location_id":         device.LocationId,
				"floor_id":            device.FloorId,
				"position_id":         device.PositionId,
				"current_map_name":    device.CurrentMapName,
				"latest_active_time":  device.LatestActiveTime,
				"service_phone":       device.ServicePhone,
				"firmware_version":    device.FirmwareVersion,
				"battery":             device.Battery,
				"created_at":          device.CreatedAt,
				"updated_at":          device.UpdatedAt,
				"device_type_title":   device.DeviceTypeTitle,
				"online":              device.Online,
				"location_name":       device.LocationName,
				"location_latitude":   device.LocationLatitude,
				"location_longitude":  device.LocationLongitude,
				"floor_name":          device.FloorName,
				"merchant_name":       device.MerchantName,
				"mode":                device.Mode,
				"mode_name":           device.ModeName,
				"sub_mode":            device.SubMode,
				"sub_mode_name":       device.SubModeName,
				"addr":                device.Addr,
				"position_name":       device.PositionName,
				"dist_km":             device.DistKm,
				"opening_hours":       device.OpeningHours,
				"coupon": map[string]interface{}{
					"name":       name,
					"sales_off":  saleoff,
					"base_price": basePrice,
					"start_at":   start_at,
					"end_at":     end_at,
				},
				"face_count": faceCount,
				"images":     device.Images,
			}
			deviceList = append(deviceList, deviceMap)
		}

		response.JSON(c, gin.H{
			"total": resp.Total,
			"list":  deviceList,
		}, err)
	} else {

		// ctx := c.Request.Context()
		searchRadius, err := dao.AdConfigDao{}.GetSearchRadius(c.Request.Context(), dal.Q)
		geoLocation, err := GeoRadius(&req.Longitude, &req.Latitude, searchRadius)
		if len(geoLocation) == 0 {
			c.JSON(http.StatusOK, gin.H{"error": "geoLocation = 0"})
			return
		}
		if err != nil {
			c.JSON(http.StatusOK, gin.H{"error": err.Error()})
			return
		}

		cli, err := client.GetDeviceAdminClient()
		if cli == nil && err != nil {
			c.JSON(http.StatusOK, gin.H{
				"total": 0,
			})
			return
		}
		// deviceClient, deviceClientErr := client.GetAdminClient()
		// if deviceClientErr != nil {
		// 	logx.Error(ctx, "connect device grpc error", logx.Any("deviceClientErr", deviceClientErr))
		// 	return
		// }

		basePrice, err := dao.AdConfigDao{}.GetBasePrice(c.Request.Context(), dal.Q)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		deviceList := make([]map[string]interface{}, 0)
		for _, v := range geoLocation {
			robotID := v.Name

			grpcReq := &devicec.DeviceListAdReq{
				RobotId:      robotID,
				MerchantId:   req.MerchantID,
				MerchantPath: req.MerchantPath,
			}
			resp, err := cli.DeviceDetailAd(c.Request.Context(), grpcReq)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}

			if len(resp.Devices) != 1 || err != nil {
				return
			}
			device := resp.Devices[0]
			// 获取位置信息
			// req := &devicec.LocationDetailReq{
			// 	LocationId: device.LocationId,
			// }
			// locationDetail, locationErr := deviceClient.LocationDetail(ctx, req)
			// if locationErr != nil || locationDetail.Location == nil {
			// 	logx.Error(ctx, "get location detail error", logx.Any("locationDetail.Location", locationDetail.Location), logx.Any("locationErr", locationErr), logx.Any("req", req), logx.Any("device", device))
			// 	return
			// }
			// 设备位置名称
			// positionMap, positionErr := deviceClient.DevicePositionName(ctx, &devicec.DevicePositionNameReq{
			// 	PositionIds: []string{device.PositionId},
			// })
			// if positionErr != nil || positionMap == nil {
			// 	logx.Error(ctx, "get position error", logx.Any("positionErr", positionErr))
			// 	return
			// }
			// var positionName string
			// if _, ok := positionMap.DevicePositionNameMap[device.PositionId]; ok {
			// 	positionName = positionMap.DevicePositionNameMap[device.PositionId]
			// }

			faceCount, err := GetDeviceFaceCount(c.Request.Context(), device.RobotId)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
			name, saleoff, start_at, end_at, _ := service.AdCoupon{}.GetCouponByContition(c.Request.Context(), time.Now().Unix(), resp.Devices[0].RobotId, "")

			logx.Info(context.Background(), "DeviceListAd", logx.Any("devicd.RobotId", device.RobotId), logx.Any("couponname", name), logx.Any("saleoff", saleoff))

			longitude, latitude := coordtransform.WGS84toGCJ02(v.Longitude, v.Latitude)
			deviceMap := map[string]interface{}{
				"id":                  device.IdentificationCode,
				"robot_id":            device.RobotId,
				"mac_addr":            device.MacAddr,
				"merchant_id":         device.MerchantId,
				"merchant_path":       device.MerchantPath,
				"identification_code": device.IdentificationCode,
				"device_type":         device.DeviceType,
				"device_ip":           device.DeviceIp,
				"location_id":         device.LocationId,
				"floor_id":            device.FloorId,
				"position_id":         device.PositionId,
				"current_map_name":    device.CurrentMapName,
				"latest_active_time":  device.LatestActiveTime,
				"service_phone":       device.ServicePhone,
				"firmware_version":    device.FirmwareVersion,
				"battery":             device.Battery,
				"created_at":          device.CreatedAt,
				"updated_at":          device.UpdatedAt,
				"device_type_title":   device.DeviceTypeTitle,
				"online":              device.Online,
				"location_name":       device.LocationName,
				"location_latitude":   latitude,
				"location_longitude":  longitude,
				"floor_name":          device.FloorName,
				"merchant_name":       device.MerchantName,
				"mode":                device.Mode,
				"mode_name":           device.ModeName,
				"sub_mode":            device.SubMode,
				"sub_mode_name":       device.SubModeName,
				"addr":                device.Addr,
				"position_name":       device.PositionName,
				"dist_km":             device.DistKm,
				"opening_hours":       device.OpeningHours,
				"coupon": map[string]interface{}{
					"name":       name,
					"sales_off":  saleoff,
					"base_price": basePrice,
					"start_at":   start_at,
					"end_at":     end_at,
				},
				"face_count": faceCount,
				"images":     device.Images,
			}

			deviceList = append(deviceList, deviceMap)
		}

		response.JSON(c, gin.H{
			"total": len(geoLocation),
			"list":  deviceList,
		}, err)
	}
}

// DeviceDetailAd 设备详情
func (Advertisement) DeviceDetailAd(c *gin.Context) {
	var req struct {
		RobotID      string `form:"robot_id"`
		MerchantID   string `form:"merchant_id"`
		MerchantPath string `form:"merchant_path"`
	}

	if err := c.ShouldBindQuery(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	cli, err := client.GetDeviceAdminClient()
	if cli == nil && err != nil {
		c.JSON(http.StatusOK, gin.H{
			"total": 0,
		})
		return
	}

	grpcReq := &devicec.DeviceListAdReq{
		RobotId:      req.RobotID,
		MerchantId:   req.MerchantID,
		MerchantPath: req.MerchantPath,
	}

	resp, err := cli.DeviceDetailAd(c.Request.Context(), grpcReq)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	basePrice, err := dao.AdConfigDao{}.GetBasePrice(c.Request.Context(), dal.Q)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// Convert devices to map format
	deviceList := make([]map[string]interface{}, 0)
	for _, device := range resp.Devices {
		faceCount, err := GetDeviceFaceCount(c.Request.Context(), device.RobotId)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		name, saleoff, start_at, end_at, _ := service.AdCoupon{}.GetCouponByContition(c.Request.Context(), time.Now().Unix(), device.RobotId, "")

		deviceMap := map[string]interface{}{
			"id":                   device.Id,
			"robot_id":             device.RobotId,
			"mac_addr":             device.MacAddr,
			"merchant_id":          device.MerchantId,
			"merchant_path":        device.MerchantPath,
			"identification_code":  device.IdentificationCode,
			"device_type":          device.DeviceType,
			"device_ip":            device.DeviceIp,
			"location_id":          device.LocationId,
			"floor_id":             device.FloorId,
			"position_id":          device.PositionId,
			"current_map_name":     device.CurrentMapName,
			"latest_active_time":   device.LatestActiveTime,
			"service_phone":        device.ServicePhone,
			"firmware_version":     device.FirmwareVersion,
			"battery":              device.Battery,
			"created_at":           device.CreatedAt,
			"updated_at":           device.UpdatedAt,
			"device_type_title":    device.DeviceTypeTitle,
			"online":               device.Online,
			"location_name":        device.LocationName,
			"location_latitude":    device.LocationLatitude,
			"location_longitude":   device.LocationLongitude,
			"floor_name":           device.FloorName,
			"merchant_name":        device.MerchantName,
			"mode":                 device.Mode,
			"mode_name":            device.ModeName,
			"sub_mode":             device.SubMode,
			"sub_mode_name":        device.SubModeName,
			"task_data_updated_at": device.TaskDataUpdatedAt,
			"addr":                 device.Addr,
			"position_name":        device.PositionName,
			"dist_km":              device.DistKm,
			"opening_hours":        device.OpeningHours,
			"coupon": map[string]interface{}{
				"name":       name,
				"sales_off":  saleoff,
				"base_price": basePrice,
				"start_at":   start_at,
				"end_at":     end_at,
			},
			"face_count": faceCount,
			"images":     device.Images,
		}
		deviceList = append(deviceList, deviceMap)
	}

	// response.JSON(c, gin.H{
	// 	"total": resp.Total,
	// 	"list":  deviceList,
	// }, err)
	response.JSON(c, deviceList[0], err)
}

const (
	// defaultFFmpegPath  = "D:/DevelopProj/FFmpeg/ffmpeg-6.1.1-full_build-shared/ffmpeg-6.1.1-full_build-shared/bin/ffmpeg"
	// defaultFFProbePath = "D:/DevelopProj/FFmpeg/ffmpeg-6.1.1-full_build-shared/ffmpeg-6.1.1-full_build-shared/bin/ffprobe"
	defaultFFmpegPath  = "/web/ffmpeg"
	defaultFFProbePath = "/web/ffprobe"
)

// ConvertMOVToMP4Bytes 调用 ffmpeg 将 MOV 的 []byte 转为 MP4 的 []byte
func ConvertMOVToMP4Bytes(movBytes []byte) ([]byte, error) {
	// 1. 创建临时输入文件（MOV）
	tmpInput, err := os.CreateTemp("", "input-*.mov")
	if err != nil {
		return nil, fmt.Errorf("failed to create temp input file: %v", err)
	}
	defer os.Remove(tmpInput.Name()) // 程序结束后删除临时文件

	// 写入 MOV 数据
	if _, err := tmpInput.Write(movBytes); err != nil {
		return nil, fmt.Errorf("failed to write MOV data: %v", err)
	}
	tmpInput.Close() // 关闭文件，确保数据写入磁盘

	// 2. 创建临时输出文件（MP4）
	tmpOutput, err := os.CreateTemp("", "output-*.mp4")
	if err != nil {
		return nil, fmt.Errorf("failed to create temp output file: %v", err)
	}
	defer os.Remove(tmpOutput.Name()) // 程序结束后删除临时文件
	tmpOutput.Close()                 // 先关闭，ffmpeg 会重新写入

	// 3. 调用 ffmpeg 进行转换（修复结尾缺失问题）
	cmd := exec.Command(
		defaultFFmpegPath,
		"-i", tmpInput.Name(), // 输入文件
		"-fflags", "+genpts", // 生成缺失的PTS（解决时间戳问题）
		"-avoid_negative_ts", "make_zero", // 处理负时间戳
		"-c:v", "libx264", // H.264 编码
		"-preset", "fast", // 编码速度
		"-crf", "23", // 质量参数
		"-c:a", "aac", // AAC 音频编码
		"-movflags", "+faststart", // 优化流式播放
		"-f", "mp4", // 强制输出格式为MP4
		"-flush_packets", "1", // 及时刷新数据包
		"-y",             // 覆盖输出文件
		tmpOutput.Name(), // 输出文件
	)

	// 捕获 ffmpeg 的错误输出（stderr）
	var stderr bytes.Buffer
	cmd.Stderr = &stderr

	// 执行命令
	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("ffmpeg failed: %v\nStderr: %s", err, stderr.String())
	}

	// 4. 读取转换后的 MP4 数据
	mp4Bytes, err := os.ReadFile(tmpOutput.Name())
	if err != nil {
		return nil, fmt.Errorf("failed to read MP4 file: %v", err)
	}

	return mp4Bytes, nil
}

// DetectMediaFormat 检测二进制数据的媒体格式（如 "mov", "mp4"）
func DetectMediaFormat(data []byte) (string, error) {
	// 1. 创建临时文件
	tmpFile, err := os.CreateTemp("", "media-*")
	if err != nil {
		return "", fmt.Errorf("failed to create temp file: %v", err)
	}
	defer os.Remove(tmpFile.Name()) // 清理临时文件

	// 2. 写入二进制数据
	if _, err := tmpFile.Write(data); err != nil {
		return "", fmt.Errorf("failed to write data: %v", err)
	}
	tmpFile.Close()

	// 3. 调用 ffprobe 检测格式
	cmd := exec.Command(
		defaultFFProbePath,
		"-v", "error", // 只输出错误信息
		"-show_entries", "format=format_name", // 查询格式名称
		"-of", "default=noprint_wrappers=1:nokey=1", // 简化输出
		tmpFile.Name(),
	)

	// 捕获输出
	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	// 执行命令
	if err := cmd.Run(); err != nil {
		return "", fmt.Errorf("ffprobe failed: %v\nStderr: %s", err, stderr.String())
	}

	// 4. 解析结果（如 "mov,mp4,m4a,3gp,3g2,mj2"）
	format := strings.TrimSpace(stdout.String())
	if format == "" {
		return "", fmt.Errorf("unknown media format")
	}

	// 提取主要格式（第一个逗号前的部分）
	primaryFormat := strings.Split(format, ",")[0]
	return primaryFormat, nil
}

// FileUpload 文件上传
// file 文件
// dir 保存路径
func (Advertisement) FileUpload(c *gin.Context) {
	file, err := c.FormFile("file")
	if err != nil {
		response.JSON(c, "", errorx.New("未获取到上传的文件", -1))
		return
	}
	logx.Info(context.Background(), "file", logx.Any("file", file.Filename))
	f, _ := file.Open()
	fmt.Println(file.Size) //50671
	orgAllBytes, _ := io.ReadAll(f)
	AllBytes := []byte{}

	// 检测格式
	format, err := DetectMediaFormat(orgAllBytes)
	if err != nil {
		logx.Error(c.Request.Context(), "Detection failed:", logx.Any("format", format), logx.Any("err", err))
		return
	} else {
		logx.Info(c.Request.Context(), "Detection sucess:", logx.Any("format", format))
	}
	// 判断图片/视频大小，超限报错
	imageFormats := map[string]bool{
		"jpeg": true,
		"jpg":  true,
		"png":  true,
		"gif":  true,
		"bmp":  true,
		"webp": true,
	}
	videoFormats := map[string]bool{
		"mp4": true,
		"mov": true,
		"avi": true,
		"mkv": true,
		"flv": true,
		"wmv": true,
		"3gp": true,
		"m4v": true,
		"ts":  true,
	}

	const (
		imageMaxSize = 2 * 1024 * 1024  // 3M
		videoMaxSize = 20 * 1024 * 1024 // 20M
	)
	lowerFormat := strings.ToLower(format)
	if imageFormats[lowerFormat] && file.Size > imageMaxSize {
		response.JSON(c, "", errorx.New("图片大小不能超过3M", -1))
		return
	}
	if videoFormats[lowerFormat] && file.Size > videoMaxSize {
		response.JSON(c, "", errorx.New("视频大小不能超过20M", -1))
		return
	}

	if format == "mov" {
		// 转换为 MP4
		AllBytes, err = ConvertMOVToMP4Bytes(orgAllBytes)
		file.Filename += ".mp4"
		if err != nil {
			logx.Error(c.Request.Context(), "Conversion failed:", logx.Any("err", err))
		} else {
			logx.Info(c.Request.Context(), "Conversion success:", logx.Any("format", format))

		}
	} else {
		AllBytes = orgAllBytes
	}
	// 保存AllBytes到d:/xx.mp4
	func() {
		f, err := os.Create("/web/xx.mp4")
		if err == nil {
			defer f.Close()
			f.Write(AllBytes)
		}
	}()
	ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
	defer cancel()
	SystemAdminClient, clientErr := client.GetSystemAdminClient()
	if clientErr != nil {
		logx.Error(c.Request.Context(), "user login system admin error", logx.Any("err", clientErr.Error()))
		response.JSON(c, nil, errorx.New("文件上传失败", -1))
		return
	}
	fileName := helper.RandString(8) + path.Ext(file.Filename)
	req := system_admin.FileUploadReq{
		FileName:   fileName,
		Dir:        c.PostForm("dir"),
		SystemName: "advertising",
	}
	fileClient, fileClientErr := SystemAdminClient.FileUpload(context.Background())
	if fileClientErr != nil {
		logx.Error(c.Request.Context(), "file upload error", logx.Any("err", fileClientErr.Error()))
		return
	}

	var status = 0
	reader := bytes.NewReader(AllBytes)
	var totalSize = int64(len(AllBytes))
	for {
		var buffSize int
		if totalSize >= 3*1024*1024 {
			buffSize = 3 * 1024 * 1024
		} else {
			buffSize = int(totalSize)
		}
		var buffer = make([]byte, buffSize) // 3M
		_, err := reader.Read(buffer)
		totalSize -= int64(buffSize)
		if err != nil {
			if err == io.EOF { //文件读取完毕，改变status
				status = FILE_END
			} else {
				panic(err)
			}
		} else {
			if buffSize < 3*1024*1024 || totalSize == 0 {
				status = FILE_END
			} else {
				status = FILE_CONTINUE
			}
		}
		if status == FILE_CONTINUE {
			req.File = buffer
			err = fileClient.Send(&req)
			if err != nil && err != io.EOF {
				response.JSON(c, nil, err)
				return
			}
			fmt.Println("continue")
		} else if status == FILE_END {
			req.File = buffer
			req.End = 1
			err = fileClient.Send(&req)
			if err != nil && err != io.EOF {
				response.JSON(c, nil, err)
				return
			}
			fmt.Println("end")
			break
		}
	}

	for {
		select {
		case <-fileClient.Context().Done():
			fmt.Println("context done")
			return
		case <-ctx.Done():
			fmt.Println("time time out ---")
			return
		default:
			msg, err := fileClient.Recv()
			// 消息处理
			if err == nil && msg != nil {
				response.JSON(c, gin.H{
					"file_url": msg.FileUrl,
				}, errorx.New(msg.Msg, int(msg.Code)))
				return
			} else {
				if err != io.EOF {
					logx.Error(c.Request.Context(), "file upload get error msg", logx.Any("err", err), logx.Any("msg", msg))
					response.JSON(c, nil, err)
					return
				}
			}
		}
	}
}

// MaterialSave 素材上传
func (Advertisement) MaterialSave(c *gin.Context) {
	var params struct {
		Name     string `json:"name"`
		FilePath string `json:"file_path"`
		Position int8   `json:"position"`
		Type     int8   `json:"type"`
		FolderID string `json:"folder_id"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	merchantID := c.GetString("merchant_id")
	merchantName := c.GetString("merchant_name")
	userID := c.GetString("user_id")
	username := c.GetString("username")

	// 获取 advertising 客户端
	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 构建 RPC 请求参数
	req := &advertisingc.MMaterialSaveReq{
		Name:         params.Name,
		FilePath:     params.FilePath,
		Position:     int32(params.Position),
		Type:         int32(params.Type),
		FolderId:     params.FolderID,
		MerchantId:   merchantID,
		MerchantName: merchantName,
		UserId:       userID,
		Username:     username,
	}

	// 调用 RPC 方法
	resp, err := client.MMaterialSave(c.Request.Context(), req)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}

	response.JSON(c, resp, nil)
}

// MaterialList 素材列表
func (Advertisement) MaterialList(c *gin.Context) {
	var params struct {
		Type     int8   `json:"type"`
		Name     string `json:"name"`
		Position int    `json:"position"`
		Status   int8   `json:"status"`
		Page     int    `json:"page"`
		Limit    int    `json:"limit"`
		FolderID string `json:"folder_id"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	merchantID := c.GetString("merchant_id")

	// 获取 advertising 客户端
	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 构建 RPC 请求参数
	req := &advertisingc.MMaterialListReq{
		Type:       int32(params.Type),
		Name:       params.Name,
		Position:   int32(params.Position),
		Status:     int32(params.Status),
		Page:       int32(params.Page),
		Limit:      int32(params.Limit),
		FolderId:   params.FolderID,
		MerchantId: merchantID,
	}

	// 调用 RPC 方法
	resp, err := client.MMaterialList(c.Request.Context(), req)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}

	response.JSON(c, gin.H{
		"count": resp.Data.Count,
		"list":  resp.Data.List,
	}, nil)
}

// MaterialDelete 素材删除
func (Advertisement) MaterialDelete(c *gin.Context) {
	var params struct {
		IDs string `json:"ids"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	merchantID := c.GetString("merchant_id")

	// 获取 advertising 客户端
	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 构建 RPC 请求参数
	req := &advertisingc.MMaterialDeleteReq{
		Ids:        params.IDs,
		MerchantId: merchantID,
	}

	// 调用 RPC 方法
	resp, err := client.MMaterialDelete(c.Request.Context(), req)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}

	response.JSON(c, nil, nil)
}

// MaterialInUseList 可用素材列表
func (Advertisement) MaterialInUseList(c *gin.Context) {
	var params struct {
		Type     int8   `json:"type"`
		Name     string `json:"name"`
		Position int    `json:"position"`
		Page     int    `json:"page"`
		Limit    int    `json:"limit"`
		FolderID string `json:"folder_id"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	merchantID := c.GetString("merchant_id")

	// 获取 advertising 客户端
	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 构建 RPC 请求参数
	req := &advertisingc.MMaterialInUseListReq{
		Type:       int32(params.Type),
		Name:       params.Name,
		Position:   int32(params.Position),
		Page:       int32(params.Page),
		Limit:      int32(params.Limit),
		FolderId:   params.FolderID,
		MerchantId: merchantID,
	}

	// 调用 RPC 方法
	resp, err := client.MMaterialInUseList(c.Request.Context(), req)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}

	response.JSON(c, gin.H{
		"count": resp.Data.Count,
		"list":  resp.Data.List,
	}, nil)
}

// AuditingMaterialCount 审核中素材统计
func (Advertisement) AuditingMaterialCount(c *gin.Context) {
	merchantID := c.GetString("merchant_id")

	// 获取 advertising 客户端
	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 构建 RPC 请求参数
	req := &advertisingc.MAuditingMaterialCountReq{
		MerchantId: merchantID,
	}

	// 调用 RPC 方法
	resp, err := client.AuditingMaterialCount(c.Request.Context(), req)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}

	response.JSON(c, gin.H{
		"count": resp.Data.Count,
	}, nil)
}

// 素材审核
func (Advertisement) MaterialAudit(c *gin.Context) {
	var params advertisingc.MaterialAuditReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	params.MerchantId = c.GetString("merchant_id")
	params.MerchantName = c.GetString("merchant_name")
	params.CreateUser = c.GetString("user_id")
	params.CreateUsername = c.GetString("username")

	cli, err := client.GetAdvertisingClient()
	if err != nil {
		err = errorx.New("广告系统连接错误", -1)
		return
	}
	rsp, rspErr := cli.MaterialAudit(c.Request.Context(), &params)
	if rspErr != nil || rsp.Code != 0 {
		err = errorx.New(rsp.Msg, int(rsp.Code))
	}

	response.JSON(c, rsp, err)
}

// 素材移动文件夹
func (Advertisement) MoveFolder(c *gin.Context) {
	var params struct {
		IDs      string `json:"ids"`
		FolderID string `json:"folder_id"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	merchantID := c.GetString("merchant_id")

	// 获取 advertising 客户端
	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 构建 RPC 请求参数
	req := &advertisingc.MMaterialMoveFolderReq{
		Ids:        params.IDs,
		FolderId:   params.FolderID,
		MerchantId: merchantID,
	}

	// 调用 RPC 方法
	resp, err := client.MoveFolder(c.Request.Context(), req)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}

	response.JSON(c, nil, nil)
}

// FolderSave 分组新增编辑
func (Advertisement) FolderSave(c *gin.Context) {
	var params struct {
		ID   string `json:"id"`
		PID  string `json:"pid"`
		Type int8   `json:"type"`
		Name string `json:"name"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	merchantID := c.GetString("merchant_id")

	// 获取 advertising 客户端
	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 构建 RPC 请求参数
	req := &advertisingc.MFolderSaveReq{
		Id:         params.ID,
		Pid:        params.PID,
		Type:       int32(params.Type),
		Name:       params.Name,
		MerchantId: merchantID,
	}

	// 调用 RPC 方法
	resp, err := client.FolderSave(c.Request.Context(), req)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}

	response.JSON(c, nil, nil)
}

// FolderList 分组列表
func (Advertisement) FolderList(c *gin.Context) {
	var params struct {
		Type int8 `json:"type"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	merchantID := c.GetString("merchant_id")

	// 获取 advertising 客户端
	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 构建 RPC 请求参数
	req := &advertisingc.MFolderListReq{
		Type:       int32(params.Type),
		MerchantId: merchantID,
	}

	// 调用 RPC 方法
	resp, err := client.FolderList(c.Request.Context(), req)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}

	response.JSON(c, resp.Data, nil)
}

// FolderDelete 分组删除
func (Advertisement) FolderDelete(c *gin.Context) {
	var params struct {
		ID string `json:"id"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	merchantID := c.GetString("merchant_id")

	// 获取 advertising 客户端
	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 构建 RPC 请求参数
	req := &advertisingc.MFolderDeleteReq{
		Id:         params.ID,
		MerchantId: merchantID,
	}

	// 调用 RPC 方法
	resp, err := client.FolderDelete(c.Request.Context(), req)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}

	response.JSON(c, nil, nil)
}

// FolderSort 分组排序
func (Advertisement) FolderSort(c *gin.Context) {
	var params struct {
		Sort []struct {
			ID   string `json:"id"`
			Sort int8   `json:"sort"`
		} `json:"sort"`
	}
	c.ShouldBindBodyWith(&params, binding.JSON)
	merchantID := c.GetString("merchant_id")

	// 获取 advertising 客户端
	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 构建排序项
	var sortItems []*advertisingc.MFolderSortItem
	for _, item := range params.Sort {
		sortItems = append(sortItems, &advertisingc.MFolderSortItem{
			Id:   item.ID,
			Sort: int32(item.Sort),
		})
	}

	// 构建 RPC 请求参数
	req := &advertisingc.MFolderSortReq{
		Sort:       sortItems,
		MerchantId: merchantID,
	}

	// 调用 RPC 方法
	resp, err := client.FolderSort(c.Request.Context(), req)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}

	response.JSON(c, nil, nil)
}

func (Advertisement) ScheduleSave(c *gin.Context) {
	var params advertisingc.SPublishScheduleSaveReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.ScheduleSave(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, err)
}

func (Advertisement) ScheduleList(c *gin.Context) {
	var params advertisingc.SPublishScheduleListReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.ScheduleList(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

func (Advertisement) ScheduleDetail(c *gin.Context) {
	var params advertisingc.SPublishScheduleDetailReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.ScheduleDetail(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp.Data, errorx.New(resp.Msg, int(resp.Code)))
}

func (Advertisement) ScheduleSimpleDetail(c *gin.Context) {
	var params advertisingc.SPublishScheduleSimpleDetailReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.ScheduleSimpleDetail(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp.Data, errorx.New(resp.Msg, int(resp.Code)))
}

func (Advertisement) ScheduleSubmit(c *gin.Context) {
	var params advertisingc.SPublishScheduleSubmitReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.ScheduleSubmit(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

func (Advertisement) End(c *gin.Context) {
	var params advertisingc.SPublishScheduleEndReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.End(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

func (Advertisement) AuditingPublishScheduleCount(c *gin.Context) {
	var params advertisingc.SAuditingPublishScheduleCountReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.AuditingPublishScheduleCount(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

func (Advertisement) PublishingPublishScheduleCount(c *gin.Context) {
	var params advertisingc.SPublishingPublishScheduleCountReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.PublishingPublishScheduleCount(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

// 支持活动 -- 创建支持活动
func (Advertisement) SupportActivityCreate(c *gin.Context) {
	var params advertisingc.SupportActivityCreateReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.SupportActivityCreate(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

// 支持活动 -- 更新支持活动
func (Advertisement) SupportActivityUpdate(c *gin.Context) {
	var params advertisingc.SupportActivityUpdateReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.SupportActivityUpdate(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

// 支持活动 -- 删除支持活动
func (Advertisement) SupportActivityDelete(c *gin.Context) {
	var params advertisingc.SupportActivityDeleteReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.SupportActivityDelete(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

// 支持活动 -- 获取活动详情
func (Advertisement) SupportActivityDetail(c *gin.Context) {
	var params advertisingc.SupportActivityDetailReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.SupportActivityDetail(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

// 支持活动 -- 获取支持活动列表
func (Advertisement) SupportActivityList(c *gin.Context) {
	var params advertisingc.SupportActivityListReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.SupportActivityList(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

// 支持活动 -- 修改活动状态
func (Advertisement) SupportActivityChangeStatus(c *gin.Context) {
	var params advertisingc.SupportActivityChangeStatusReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.SupportActivityChangeStatus(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

// 支持活动 -- 检查同名活动是否存在
func (Advertisement) SupportActivitySameNameExist(c *gin.Context) {
	var params advertisingc.SupportActivitySameNameExistReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.SupportActivitySameNameExist(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

// 支持活动 -- 根据素材ID获取关联的活动列表
func (Advertisement) SupportActivityGetActivityListByMaterialIDs(c *gin.Context) {
	var params advertisingc.SupportActivityGetActivityListByMaterialIDsReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.SupportActivityGetActivityListByMaterialIDs(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

// 支持活动 -- 获取活动关联的素材ID列表
func (Advertisement) SupportActivityGetMaterialIDsList(c *gin.Context) {
	var params advertisingc.SupportActivityGetMaterialIDsListReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.SupportActivityGetMaterialIDsList(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

// 支持活动 -- 更新活动素材ID
func (Advertisement) SupportActivitySetMaterialIDs(c *gin.Context) {
	var params advertisingc.SupportActivitySetMaterialIDsReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.SupportActivitySetMaterialIDs(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

func (Advertisement) ScheduleCalendar(c *gin.Context) {
	var params advertisingc.SPublishScheduleCalendarReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.ScheduleCalendar(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

func (Advertisement) Test(c *gin.Context) {
	var params advertisingc.SPublishScheduleTestReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.Test(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp, nil)
}

func (Advertisement) ScheduleListOrderInfo(c *gin.Context) {
	var params advertisingc.SPublishScheduleListOrderInfoReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.ScheduleListOrderInfo(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp.Data, errorx.New(resp.Msg, int(resp.Code)))
}

func (Advertisement) ScheduleDetailOrderInfo(c *gin.Context) {
	var params advertisingc.SPublishScheduleDetailOrderInfoReq
	c.ShouldBindBodyWith(&params, binding.JSON)

	client, err := client.GetAdvertisingClient()
	if err != nil {
		response.JSON(c, nil, err)
		return
	}

	resp, err := client.ScheduleDetailOrderInfo(c.Request.Context(), &params)
	if err != nil {
		response.JSON(c, nil, err)
		return
	}
	// 检查 RPC 响应
	if resp.Code != 0 {
		response.JSON(c, nil, errorx.New(resp.Msg, int(resp.Code)))
		return
	}
	response.JSON(c, resp.Data, errorx.New(resp.Msg, int(resp.Code)))
}
